﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using NeturalMath.Expressions;
using NeturalMath.Properties;

namespace NeturalMath.Language
{
    public class Parser
    {
        #region Local Members

        private readonly Lexer _lex;
        private readonly MathRuntime _runtime;
        private MathExpression _lastExpression;
        private Token _lastToken;

        private ITokenProvider _provider;

        #endregion

        #region Constructors

        public Parser(Lexer lex,MathRuntime runtime)
        {
            _lex = lex;
            _provider = _lex;
            _runtime = runtime;
        }

        public Parser(TextReader reader, MathRuntime runtime)
        {
            _lex = new Lexer(reader);
            _provider = _lex;
            _runtime = runtime;
        }

        public MathRuntime Runtime
        {
            get { return _runtime; }
        }

        #endregion

        #region Public Methods

        public IEnumerable<MathExpression> GetAllExpressions()
        {
            var expression = GetNextExpression();
            while (expression!=null)
            {
                yield return expression;
                expression = GetNextExpression();
            }
        }

        public MathExpression GetNextExpression()
        {
            var token = GetNextToken();
            while (token.Type == TokenType.NewLine)
                token = GetNextToken();
            return ParseAndChain(token);
        }

        private MathExpression ParseAndChain(Token token)
        {
            _lastExpression = ParseTokenExpression(token);

            var next = GetNextToken();

            if (IsTerminator(next.Type))
            {
                PushNextToken(next);
                return _lastExpression;
            }

            return ParseAndChain(next);
        }

        private MathExpression ParseTokenExpression(Token token)
        {
            switch (token.Type)
            {
                case TokenType.CommaSeperator:
                case TokenType.EOF:
                    return null;

                case TokenType.SetEnd:
                case TokenType.GroupingEnd:
                case TokenType.NewLine:
                case TokenType.DomainEnd:
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException,"Invalid Syntax");

                case TokenType.AssignmentOperator:
                    return ParseAssignment();

                case TokenType.DomainBegin:
                    return ParseDomain();

                case TokenType.SymbolicLookup:
                case TokenType.Identifier:
                    return ParseIdentifier(token);

                case TokenType.GroupingBegin:
                    return ParseGrouping();

                case TokenType.SetBegin:
                    return ParseSet();
                
                case TokenType.ImportKeyword:
                    return ParseImportKeyword();

                case TokenType.NotOperator:
                    return ParseUnaryNot();

                case TokenType.IntegralOperator:
                    return ParseIntegrals();

                case TokenType.DerivitiveOperator:
                    return ParseDerivitives();

                case TokenType.DefKeyword:
                    return ParseDefKeyword();

                case TokenType.PrintKeyword:
                    return ParsePrintKeyword();

                case TokenType.FactorialOperator:
                    return ParseFactorial();

                case TokenType.UnitOperator:
                    return ParseUnit(token);

                case TokenType.FalseConstant:
                case TokenType.StringConstant:
                case TokenType.TrueConstant:
                case TokenType.VoidConstant:
                case TokenType.NumericConstant:
                    return ParseConstant(token);

                case TokenType.ConstantAccess:
                case TokenType.GlobalAccess:
                case TokenType.PrivateAccess:
                case TokenType.ReadOnlyAccess:
                case TokenType.PublicAccess:
                    return ParseDeclaration(token);

                case TokenType.RangeOperator:
                case TokenType.AddOperator:
                case TokenType.AndOperator:
                case TokenType.SubtractOperator:
                case TokenType.MultiplyOperator:
                case TokenType.DivideOperator:
                case TokenType.PowerOperator:
                case TokenType.ModulusOperator:
                case TokenType.IsEqualOperator:
                case TokenType.NotEqualOperator:
                case TokenType.GreaterThanOperator:
                case TokenType.GreaterThanOrEqualOperator:
                case TokenType.LessThanOperator:
                case TokenType.LessThanOrEqualOperator:
                case TokenType.OrOperator:
                case TokenType.XorOperator:
                    return ParseBinaryOperator(token);

                default:
                    throw new SyntaxException(ErrorCodes.UnrecognizedTokenType, "Unrecognized token type");
            }
        }

        #endregion

        #region Token Parsing Methods

        #region Member Declaration Methods

        private AccessModifierExpression ParseDeclaration(Token token, Scope scope = default(Scope))
        {
            switch (token.Type)
            {
                case TokenType.PublicAccess:
                    if (!scope.Equals(Scope.Private))
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                    return ParseDeclaration(GetNextToken(), Scope.Public);
                
                case TokenType.ConstantAccess:
                    if (scope.Modifier!=AccessabilityModifiers.None)
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                    switch(scope.Accessability)
                    {
                        case AccessabilityLevels.Private:
                            scope = Scope.PrivateConst;
                            break;
                        case AccessabilityLevels.Public:
                            scope = Scope.PublicConst;
                            break;
                        case AccessabilityLevels.Global:
                            scope = Scope.GlobalConst;
                            break;
                    }

                    return ParseDeclaration(GetNextToken(), scope);
                
                case TokenType.ReadOnlyAccess:
                    if (scope.Modifier != AccessabilityModifiers.None)
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                    if (scope.Accessability!=AccessabilityLevels.Public)
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                    return ParseDeclaration(GetNextToken(), Scope.PublicReadOnly);
                
                case TokenType.GlobalAccess:
                    if (!scope.Equals(Scope.Private))
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                    return ParseDeclaration(GetNextToken(), Scope.Global);
                
                case TokenType.PrivateAccess:
                    if (!scope.Equals(Scope.Private))
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                    return ParseDeclaration(GetNextToken());
            }


            var expression = ParseTokenExpression(token);
            var identifier = expression as LookupExpression;
            if (identifier==null)
                throw new SyntaxException(ErrorCodes.ExpectedIndentifier, Resources.ExpectedIdentifier);

            return new AccessModifierExpression(identifier, scope,Runtime);
        }

        private IEnumerable<MathExpression> GetParameters()
        {
            var parameters = new List<MathExpression>();

            //var next = GetNextToken();

            var tokens = new List<Token>();

            var depth = 1;
            while (depth > 0)
            {
                var next = GetNextToken();


                if (next.Type == TokenType.GroupingEnd)
                {
                    depth--;
                    if (depth > 0)
                        tokens.Add(next);
                    continue;
                }

                if (next.Type == TokenType.GroupingBegin)
                {
                    depth++;
                    tokens.Add(next);
                    continue;
                }

                if (next.Type == TokenType.EOF)
                    throw new SyntaxException(ErrorCodes.UnexpectedEndOfFile, Resources.UnexpectedEOF);

                if (next.Type == TokenType.NewLine)
                    throw new SyntaxException(ErrorCodes.UnexpectedEndOfLine, Resources.LexicalUnexpectedEndOfLine);

                tokens.Add(next);
            }

            var oldProvider = _provider;
            _provider = new ListTokenProvider(tokens);

            var listToken = GetNextToken();
            while (listToken.Type != TokenType.EOF)
            {
                parameters.Add(ParseAndChain(listToken));
                listToken = GetNextToken();
            }

            _provider = oldProvider;
            return parameters;
        }

        private DomainExpression ParseDomain()
        {
            var expressions = new List<MathExpression>();
            var token = GetNextToken();
            while (token.Type == TokenType.NewLine)
                token = GetNextToken();
            
            while (token.Type!=TokenType.DomainEnd)
            {
                if (token.Type == TokenType.EOF)
                    throw new SyntaxException(ErrorCodes.UnexpectedEndOfFile, Resources.UnexpectedEOF);

                var nextExpression = ParseAndChain(token);
                if (nextExpression!=null)
                    expressions.Add(nextExpression);

                do 
                {
                    token = GetNextToken();
                }
                while (token.Type == TokenType.NewLine);
            }

            return new DomainExpression(expressions, Runtime);
        }


        #endregion

        #region Identifiers and Constants

        //private CreateRangeExpression ParseRange(Token token)
        //{
        //    var last = _lastExpression;
        //    if (last == null)
        //        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Expected a starting value");

        //    _lastExpression=null;
        //    var lastValue = last as ValueExpression;
        //    if (lastValue == null)
        //        throw new SyntaxException(ErrorCodes.ExpectedIndentifier, "Expected value");

        //    var next = GetNextToken();
        //    var nextValue = ParseTokenExpression(next) as ValueExpression;
        //    if (nextValue == null)
        //        throw new SyntaxException(ErrorCodes.ExpectedIndentifier, "Expected value");

        //    Debug.Print("return: {0}->{1}", lastValue,nextValue);

        //    return new CreateRangeExpression(lastValue, nextValue, Runtime);
        //}

        private LookupExpression ParseIdentifier(Token token)
        {
            var name = token.Text;

            var next = GetNextToken();
            if (next.Type == TokenType.GroupingBegin)
                return ParseFunction(name);

            PushNextToken(next);
            if (token.Type == TokenType.SymbolicLookup)
                return new SymbolicLookupExpression(name, Runtime);

            return new LookupExpression(name, Runtime);
        }

        private FunctionUseExpression ParseFunction(string functionName)
        {
            // Create parameters list
            var parameters = GetParameters();

            return new FunctionUseExpression(functionName, parameters,Runtime);
        }

        private ValueExpression ParseConstant(Token token)
        {
            ConstantValueExpression constant;
            switch (token.Type)
            {
                case TokenType.FalseConstant:
                    constant = new ConstantValueExpression(Runtime.False, Runtime);
                    break;
                case TokenType.StringConstant:
                    constant = new ConstantValueExpression(Runtime.NewString(token.RawText), Runtime);
                    break;
                case TokenType.TrueConstant:
                    constant = new ConstantValueExpression(Runtime.True, Runtime);
                    break;
                case TokenType.VoidConstant:
                    constant = new ConstantValueExpression(Runtime.Void, Runtime);
                    break;
                case TokenType.NumericConstant:
                    constant = new ConstantValueExpression(Runtime.NewNumber(double.Parse(token.Text)), Runtime);
                    break;
                default:
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
            }
            // peek at the next token
            var next = GetNextToken();
            if (token.Type == TokenType.NumericConstant)
            {
                if (next.Type == TokenType.Identifier && next.Text == "i")
                {
                    constant = new ConstantValueExpression(Runtime.NewComplex(0, double.Parse(token.Text)), Runtime);
                }
                //else if (next.Type==TokenType.RangeOperator)
                //{
                //    _lastExpression = constant;
                //    return ParseRange(next);
                //}
                else
                    PushNextToken(next);
            }
            else
                PushNextToken(next);

            return constant;
        }

        public AssignmentExpression ParseAssignment()
        {
            var lastExpression = _lastExpression as LookupExpression;
            if (lastExpression == null)
                throw new SyntaxException(ErrorCodes.ExpectedIndentifier, Resources.ExpectedIdentifier);

            var next = GetNextToken();
            var expression = ParseAndChain(next);
            var valueExpression = expression as ValueExpression;
            if (valueExpression == null)
                throw new SyntaxException(ErrorCodes.ExpectedIndentifier, "Expected value");

            return new AssignmentExpression(lastExpression, valueExpression, Runtime);
        }

        #endregion

        #region Operators

        private MathExpression ParseIntegrals()
        {
            throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
        }

        private MathExpression ParseDerivitives()
        {
            throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
        }

        private MathExpression ParseSet()
        {
            var values = new List<MathExpression>();
            var token = GetNextToken();
            while (token.Type!=TokenType.SetEnd)
            {
                if (token.Type == TokenType.CommaSeperator)
                {
                    token = GetNextToken();
                    if (token.Type == TokenType.CommaSeperator)
                        throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
                }

                if (IsTerminator(token.Type))
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");

                var expression = ParseAndChain(token);
                
                if (expression!=null)
                    values.Add(expression);

                token = GetNextToken();
            }
            var value = Runtime.NewSet(values.Select(v => v.Execute()));
            return new ConstantValueExpression(value, Runtime);
        }


        private MathExpression ParseGrouping()
        {
            var next = GetNextToken();
            var groupExpression = ParseAndChain(next);

            // clear grouping token
            next = GetNextToken();

            // Check if value is a complex number
            var valExpression = groupExpression as ConstantValueExpression;
            if (next.Type==TokenType.CommaSeperator && valExpression!=null)
            {
                var value = valExpression.Value as NumberValue;
                if (value==null)
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");

                next = GetNextToken();
                if (next.Type != TokenType.NumericConstant)
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Expected numeric constant!");
                
                var expression = ParseConstant(next);
                var constant = expression as ConstantValueExpression;
                if (constant == null)
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException,
                                              "Expected Numeric constant to be returned");

                var complex = constant.Value as ComplexValue;
                if (complex == null)
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");

                next = GetNextToken();
                if (next.Type != TokenType.GroupingEnd)
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Expected grouping end");

                return new ConstantValueExpression(Runtime.NewComplex(value, complex.I),Runtime);
            }

            return groupExpression;
        }

        private ValueExpression ParseBinaryOperator(Token operatorToken)
        {
            var left = (ValueExpression)_lastExpression;
            _lastExpression = null;

            var thisToken = operatorToken;
            var next = GetNextToken();

            if (left == null && operatorToken.Type == TokenType.SubtractOperator)
                return ParseUnaryMinus(next);

            ValueExpression right;
            switch (next.Type)
            {
                case TokenType.GroupingBegin:
                    right = (ValueExpression) ParseAndChain(next);
                    break;
                case TokenType.SubtractOperator:
                    next = GetNextToken();
                    right = ParseUnaryMinus(next);
                    break;
                default:
                    right = (ValueExpression) ParseTokenExpression(next);
                    break;
            }

#if DEBUG
            Debug.Print("return: {0}{1}{2}",left,thisToken.Type,right);
#endif

            switch (thisToken.Type)
            {
                case TokenType.AddOperator:
                    return new AddOperation(left, right, Runtime);
                case TokenType.AndOperator:
                    return new AndOperation(left, right, Runtime);
                case TokenType.SubtractOperator:
                    return new SubtractOperation(left, right, Runtime);
                case TokenType.MultiplyOperator:
                    return new MultiplyOperation(left, right, Runtime);
                case TokenType.DivideOperator:
                    return new DivideOperation(left, right, Runtime);
                case TokenType.PowerOperator:
                    return new PowerOperation(left, right, Runtime);
                case TokenType.ModulusOperator:
                    return new ModuloOperation(left, right, Runtime);
                case TokenType.IsEqualOperator:
                    return new EqualsOperation(left, right, Runtime);
                case TokenType.NotEqualOperator:
                    return new NotEqualsOperation(left, right, Runtime);
                case TokenType.GreaterThanOperator:
                    return new GreaterThanOperation(left, right, Runtime);
                case TokenType.GreaterThanOrEqualOperator:
                    return new GreaterThanOrEqualsOperation(left, right, Runtime);
                case TokenType.LessThanOperator:
                    return new LessThanOperation(left, right, Runtime);
                case TokenType.LessThanOrEqualOperator:
                    return new LessThanOrEqualsOperation(left, right, Runtime);
                case TokenType.OrOperator:
                    return new OrOperation(left, right, Runtime);
                case TokenType.XorOperator:
                    return new XorOperation(left, right, Runtime);
                case TokenType.RangeOperator:
                    return new CreateRangeExpression(left, right, Runtime);
                default:
                    throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Unknown syntax exception");
            }
        }

        private UnaryMinusExpression ParseUnaryMinus(Token token)
        {
            var expression = (ValueExpression)ParseAndChain(token);

#if DEBUG
            Debug.Print("return: -{0}", expression);
#endif

            return new UnaryMinusExpression(expression, Runtime);
        }

        private NotOperatorExpression ParseUnaryNot()
        {
            var token = GetNextToken();
            var expression = (ValueExpression)ParseAndChain(token);

#if DEBUG
            Debug.Print("return: ~{0}", expression);
#endif

            return new NotOperatorExpression(expression, Runtime);
        }

        private FactorialExpression ParseFactorial()
        {
            var lastExpression = _lastExpression;
            _lastExpression = null;

            var identifier = lastExpression as ValueExpression;
            if (identifier == null)
                throw new SyntaxException(ErrorCodes.ExpectedIndentifier, Resources.ExpectedIdentifier);
            
            return new FactorialExpression(identifier,Runtime);
        }


        private ValueExpression ParseUnit(Token token)
        {
            var last = (ValueExpression) _lastExpression;

            var next = GetNextToken();
            var measure = ParseAndChain(next) as ValueExpression;

            var unit = Runtime.Measurements.CreateMeasure(measure);
            if (last is ConstantValueExpression || last is LookupExpression)
            {
                _lastExpression = null;
                return new UnitOperatorExpression(last, unit, Runtime);
            }

            return new ConstantValueExpression(Runtime.NewUnit(1,unit), Runtime);
        }

        #endregion

        #region Keyword Methods

        private PrintKeywordExpression ParsePrintKeyword()
        {
            var parameters = new List<MathExpression>();
            var next = GetNextToken();
            while (next.Type != TokenType.NewLine && next.Type!=TokenType.EOF)
            {
                parameters.Add(ParseAndChain(next));
                next = GetNextToken();
            }

            return new PrintKeywordExpression(parameters, Runtime);
        }

        private DefKeywordExpression ParseDefKeyword()
        {
            var parameters = new List<MathExpression>();
            var next = GetNextToken();
            while (next.Type != TokenType.NewLine && next.Type != TokenType.EOF)
            {
                parameters.Add(ParseAndChain(next));
                next = GetNextToken();
            }

            return new DefKeywordExpression(parameters, Runtime);
        }

        private ImportKeywordExpression ParseImportKeyword()
        {
            throw new SyntaxException(ErrorCodes.GenericSyntaxException, "Invalid Syntax");
        }

        #endregion

        #region Helpers

        private Token GetNextToken()
        {
            if (_lastToken!=null)
            {
                var tmp = _lastToken;
                _lastToken = null;
                return tmp;
            }

            var token= _provider.GetToken();
#if DEBUG
            Debug.Print("get: {0}\t\t{1}" , token.Type,token.RawText);
#endif
            return token;
        }

        private void PushNextToken(Token token)
        {
#if DEBUG
            Debug.Print("push: " + token.Type);
#endif

            if (_lastToken != null)
                throw new InvalidOperationException();
            _lastToken = token;


        }

        private bool IsTerminator(TokenType type)
        {
            switch (type)
            {
                case TokenType.CommaSeperator:
                case TokenType.EOF:
                case TokenType.NewLine:
                case TokenType.GroupingEnd:
                case TokenType.DomainEnd:
                case TokenType.SetEnd:
                    return true;
                default:
                    return false;
            }
        }

        #endregion


        #endregion
    }
}
